
#include "Interpreter.h"
#include <iostream>
#include "Tokenizer.h"
#include <cctype>
#include <cassert>
#include "InfixToRPN.h"
#include <algorithm>

Interpreter* Interpreter::mIP = NULL;

void lower(char& c)
{
	//
	// Ignore characters between start and end quotes "XxXx"
	//
	// This static flag is false before and after every query
	// coz every start " is followed by an end "
	//
	static bool toIgnore = false;
	if(c == '"')
	{
		if(toIgnore == false)
			toIgnore = true;
		else
			toIgnore = false;

		return;
	}

	if(toIgnore == false)
		c = tolower(c);
}

int Interpreter::Interpret(std::string& query)
{
	// convert to lower case
	std::for_each(query.begin(), query.end(), &lower);
	//std::cout << "Lowered query: " << query << std::endl;

	//TODO: handle the fact that the dot operator is not a tokenizer, and
	//handling the WHERE is easier if the tableName.attribName is one token
	
	mTokens.clear();
	size_t queryPtr = 0;
	size_t pos;
	std::string tempStr;

	while(queryPtr != query.size())
	{
		if((pos = query.find_first_of('"', queryPtr)) == std::string::npos)
		{
			// tokenize entire substring till the end, and update string ptr
			tempStr = query.substr(queryPtr);
			Tokenizer(tempStr, mTokens);
			queryPtr = query.size();
		}
		else
		{
			// tokenize the substring till the " character, and update string ptr
			tempStr = query.substr(queryPtr, pos - queryPtr);
			Tokenizer(tempStr, mTokens);
			queryPtr = pos;
#if 0
			// add the starting " as a token, and update string ptr
//caribou			mTokens.push_back("\"");
			++queryPtr;

			// find the closing "
			pos = query.find_first_of('"', queryPtr);
			assert(pos != std::string::npos); // start " must have a matching end "

			// add the quoted substring as one token, and update string ptr
			mTokens.push_back(query.substr(queryPtr, pos - queryPtr));
			queryPtr = pos;

			// add the ending " as a token, and update string ptr
//caribou			mTokens.push_back("\"");
			++queryPtr;
#else
			
			// find the closing "
			pos = query.find_first_of('"', queryPtr+1);
			assert(pos != std::string::npos); // start " must have a matching end "

			mTokens.push_back(query.substr(queryPtr, pos - queryPtr + 1));

			queryPtr = pos + 1; // after the ending "
#endif
		}
	}

	/*std::cout << "Tokens are: " << std::endl;
	for(unsigned int i = 0; i < mTokens.size(); ++i)
		std::cout << mTokens[i] << std::endl;
	std::cout << std::endl;*/

	
	if(mTokens.empty())
		return -1;

	if(mTokens[0].compare("create") == 0)
	{
		std::cout << "CREATE" << std::endl;
		return HandleCreate();
	}
	else if(mTokens[0].compare("drop") == 0)
	{
		std::cout << "DROP" << std::endl;
		return HandleDrop();
	}
	else if(mTokens[0].compare("select") == 0)
	{
		std::cout << "SELECT" << std::endl;
		return HandleSelect();
	}
	else if(mTokens[0].compare("delete") == 0)
	{
		std::cout << "DELETE" << std::endl;
		return HandleDelete();
	}
	else if(mTokens[0].compare("insert") == 0)
	{
		std::cout << "INSERT" << std::endl;
		return HandleInsert();
	}
	else
	{
		std::cout << "Invalid query!" << std::endl;
		return -1;
	}
}

int Interpreter::HandleCreate()
{
	// create table table-name(attribute-name data-type, ...)

	assert(mTokens[1].compare("table") == 0);
	assert(mTokens[3].compare("(") == 0);
	
	std::vector<std::string> fieldNames;
	std::vector<enum FIELD_TYPE> fieldTypes;
	
	unsigned int i = 4;
	while(i < mTokens.size())
	{
		if(mTokens[i].compare(")") == 0)
			break;
		else if(mTokens[i].compare(",") == 0)
			++i;

		fieldNames.push_back(mTokens[i]); 
		++i;
		if(mTokens[i].compare("int") == 0)
			fieldTypes.push_back(INT);
		else
			fieldTypes.push_back(STR20);
		++i;
	}
	
	Schema schema(fieldNames, fieldTypes);
	std::cout << "Creating table: " << mTokens[2] << " with schema: \n" 
			<< schema << std::endl;

	if(mSchemaManager.createRelation(mTokens[2], schema) == NULL)
		return -1;
	
	std::cout << "Created table successfully!" << std::endl;

	return 0;
}

int Interpreter::HandleDrop()
{
	// drop table table-name

	assert(mTokens.size() == 3);
	assert(mTokens[1].compare("table") == 0);

	std::cout << "Dropping table: " << mTokens[2] << std::endl;
	
	if(mSchemaManager.deleteRelation(mTokens[2]) == false)
		return -1;

	return 0;
}

int Interpreter::HandleSelect()
{
	//TODO: if multiple tables in FROM, if no OR in WHERE, push WHERE down

	unsigned int fromIndex = mTokens.size(), whereIndex = mTokens.size(), 
		orderByIndex = mTokens.size();
	
	// check if duplicate elimination is required (DISTINCT after SELECT)
	bool distinct = false;
	if(mTokens[1].compare("distinct") == 0)
		distinct = true;

	unsigned int idx = 1;

	// start searching for FROM from index=1
	while(idx < mTokens.size())
	{
		if(mTokens[idx].compare("from") == 0)
		{
			fromIndex = idx;
			break;
		}
		++idx;
	}

	// check if projection is not required (* before FROM)
	bool projection = true;
	if(mTokens[fromIndex - 1].compare("*") == 0)
		projection = false;

	//TODO: fill in a vector with attribs to project here

	// start searching for WHERE from index = fromIndex + 1
	idx = fromIndex + 1;
	while(idx < mTokens.size())
	{
		if(mTokens[idx].compare("where") == 0)
		{
			whereIndex = idx;
			break;
		}
		++idx;
	}

	// start searching for ORDER BY from index = fromIndex + 1
	idx = fromIndex + 1;
	string orderbycolumn = "";
	while(idx < mTokens.size())
	{
		if(mTokens[idx].compare("order") == 0)
		{
			if(mTokens[idx+1].compare("by") == 0)
			{
				orderByIndex = idx;
				if (idx + 2 < mTokens.size ())			// there is some column to order by
					orderbycolumn = mTokens [idx + 2];
				break;
			}
		}
		++idx;
	}

	unsigned int fromEnd = std::min(whereIndex, orderByIndex), fromIter = fromIndex+1;
	std::vector<std::string> tables;
	while(fromIter != fromEnd)
	{
		if(mTokens[fromIter][0] == ',')
			++fromIter;
		
		tables.push_back(mTokens[fromIter]);
		++fromIter;
	}
	assert(tables.size() > 0);

	// get relation pointers for the tables
	std::vector<Relation*> relationPtrVec;
	for(unsigned int i = 0; i < tables.size(); ++i)
	{
		relationPtrVec.push_back(mSchemaManager.getRelation(tables[i]));
		if(relationPtrVec.back() == NULL)
		{
			std::cout << "Table pointer came out NULL for: " << tables[i] << std::endl;
			return -1;
		}
	}

	// get the sizes (in blocks and tuples) of the relations
	std::vector<std::pair<unsigned int, unsigned int> > relationBlockTupleCountVec;
	for(unsigned int i = 0; i < tables.size(); ++i)
	{
		relationBlockTupleCountVec.push_back(std::make_pair(
						relationPtrVec[i]->getNumOfBlocks(), relationPtrVec[i]->getNumOfTuples()));
	}

	bool needJoin = false;
	if(tables.size() > 1)
		needJoin = true;

	// convert WHERE clause from infix to RPN
	std::vector<std::string> whereRPN;
	if(whereIndex != mTokens.size())
	{
		// WHERE clause spans from whereIndex + 1 to orderByIndex (might be mTokens.size())
		std::vector<std::string> whereInfix(mTokens.begin() + whereIndex + 1, 
											mTokens.begin() + orderByIndex);
		if(shunting_yard(whereInfix, whereRPN) == true)
		{
			std::cout << "RPN notation of the WHERE clause: " << std::endl;
			for(unsigned int i = 0; i < whereRPN.size(); ++i)
			{
				std::cout << whereRPN[i] << " ";
			}
			std::cout << std::endl;
		}
		else
		{
			std::cout << "Conversion to RPN failed!" << std::endl;
			return -1;
		}
	}

	if(needJoin == false)		// no join means there is only 1 table
	{
		//TODO: need to handle duplicate elimination, projection, sorting...
		// tanzir
		assert (orderByIndex < mTokens.size ());
		assert (tables.size () == 1);
		selectwithorderby (tables [0], orderbycolumn);
	}
	else
	{
		//TODO: assume that cross join queries don't have the WHERE clause for now
		if(whereIndex == mTokens.size())
		{
			std::cout << "Initiating cross join..." << std::endl;

			// create new schema and relation for output, then create tuples
			std::string resultName = GetCrossJoinedName(tables);
			std::cout << "Creating cross-joined table: " << resultName << std::endl;

			std::vector<std::string> resultFieldNames;
			std::vector<enum FIELD_TYPE> resultFieldTypes;
			GetCrossJoinedSchema(tables, resultFieldNames, resultFieldTypes);
			Schema resultSchema(resultFieldNames, resultFieldTypes);

			std::cout << resultSchema << std::endl;

			if(mSchemaManager.createRelation(resultName, resultSchema) == NULL)
				return -1;
			
			Tuple resultTuple = mSchemaManager.getRelation(resultName)->createTuple();

			int totalBlocksNeeded = 0;
			for(unsigned int i = 0; i < tables.size(); ++i)
			{
				totalBlocksNeeded += relationBlockTupleCountVec[i].first;
			}

			if(totalBlocksNeeded <= mMem.getMemorySize())
			{
				// perform cross join in memory
				std::cout << "In memory operation..." << std::endl;

				// since its all in-memory, join order isn't important
				// just do a cartesian product of all tuples and print to output

				std::vector<std::vector<Tuple> > tuplesOfTables(tables.size());
				for(unsigned int i = 0; i < tables.size(); ++i)
				{
					relationPtrVec[i]->getBlocks(0, 0, relationBlockTupleCountVec[i].first);
					tuplesOfTables[i] = mMem.getTuples(0, relationBlockTupleCountVec[i].first);
				}

				// determine the number of tuples in the result relation (cartesian product)
				unsigned int resultTuplesCount = 1;
				for(unsigned int i = 0; i < tables.size(); ++i)
					resultTuplesCount *= tuplesOfTables[i].size();

				std::cout << "Expecting " << resultTuplesCount 
						<< " tuples in joined table" << std::endl;
				//
				// The below linearization business is done in order to dynamically
				// perform a cartesian product of variable number of sets.
				// This is done by converting the n-dimensional space into a linear
				// 1-D space where each n-D point is mapped to a point in the 1-D space
				// using the index of the point. Its like how the elements of a 2-D array
				// are accessed if the elements are stored in a 1-D array (i*rowCount + j)
				// Here, its tbl0 + weight1*tbl1 + weight2*tbl2 ...
				//

				// determine the weight of the tables in the linearization
				std::vector<unsigned int> weight(tables.size());
				weight[0] = 1;
				for(unsigned int tbl = 1; tbl < tables.size(); ++tbl)
					weight[tbl] = weight[tbl-1]*tuplesOfTables[tbl-1].size();

				int resultFieldIdx = 0;
				for(unsigned int i = 0; i < resultTuplesCount; ++i)
				{
					// generate the joined Tuple here
					// find the tuple to use for each of the tables
					// read sequentially the fields of each tuple and
					// set the field to the result tuple
					// output the tuple

					resultFieldIdx = 0;
					
					// for each table
					for(unsigned int tbl = 0; tbl < tables.size(); ++tbl)
					{
						// use a tuple from the table (linearized tuples with index i)
						Tuple& tempTuple = tuplesOfTables[tbl]
											[(i/weight[tbl]) % tuplesOfTables[tbl].size()];
						
						// grab the fields of the tuple and set to result tuple
						for(int fld = 0; fld < tempTuple.getNumOfFields(); ++fld)
						{
							if(resultFieldTypes[resultFieldIdx] == INT)
								resultTuple.setField(resultFieldIdx, tempTuple.getField(fld).integer);
							else
								resultTuple.setField(resultFieldIdx, *(tempTuple.getField(fld).str));

							++resultFieldIdx;
						}
					}
					assert(resultFieldIdx == resultTuple.getNumOfFields());

					std::cout << resultTuple << std::endl;
				}
			}
			else
			{
				//TODO: need one pass algorithm for cross join
				// need to figure out the cross-join order
			}

			std::cout << "Cross-join done! Wrapping up..." << std::endl;
			mSchemaManager.deleteRelation(resultName);
		}
		else
		{
			//TODO: need natural join
		}
	}
	return 0;
}

int Interpreter::HandleDelete()
{
	assert(mTokens[1].compare("from") == 0);
	
	Relation* relationPtr = mSchemaManager.getRelation(mTokens[2]);
	if(relationPtr == NULL)
		return -1;
	
	int retVal;

	if(mTokens.size() == 3) // DELETE FROM table-name
	{
		if(relationPtr->deleteBlocks(0) == false)
			retVal = -1;
		else
			retVal = 0;
	}
	else
	{
		//TODO: handle WHERE clause
		// seems like DELETE can take all the burden of handling holes
		// DELETE has to take 2*B(R) time to perform, coz the entire table
		// is read and written. Why not pack all tuples into blocks
		// The packing happens in main memory, which is not what we care about
		// The eventual number of disk I/Os will be the same, the only overhead
		// will be the eventual deletion of those few blocks at the end of the
		// table which aren't needed because the table now fits in fewer blocks!
		// At the same time, fewer blocks are written after deletion due to packing

		// DELETE FROM table-name WHERE where-clause

		std::vector<std::string> whereInfix(mTokens.begin() + 4, mTokens.end());
		std::vector<std::string> whereRPN;
		if(shunting_yard(whereInfix, whereRPN) == true)
		{
			std::cout << "RPN notation of the WHERE clause: " << std::endl;
			for(unsigned int i = 0; i < whereRPN.size(); ++i)
			{
				std::cout << whereRPN[i] << " ";
			}
			std::cout << std::endl;
		}
		else
		{
			std::cout << "Conversion to RPN failed!" << std::endl;
			return -1;
		}
	
		//
		// Lets not worry about holes for now
		// Read one block at a time
		// Check its tuples for WHERE condition
		// nullify the desired tuple
		// Write block back to disk if any tuple nullified
		//
		Block* blockPtr;
		std::vector<Tuple> tuplesInBlock;
		bool blockModified = false;
		for(int i = 0; i < relationPtr->getNumOfBlocks(); ++i)
		{
			// read relation block i to mem block 1
			relationPtr->getBlock(i, 1); 

			blockPtr = mMem.getBlock(1);
			tuplesInBlock = blockPtr->getTuples();

			blockModified = false;
			for(unsigned int j = 0; j < tuplesInBlock.size(); ++j)
			{
				if(!tuplesInBlock[j].isNull() && 
						CheckWhereOnTuple(tuplesInBlock[j], whereRPN))
				{
					tuplesInBlock[j].null();
					blockModified = true;
				}
			}
			
			if(blockModified == true)
			{
				blockPtr->setTuples(tuplesInBlock);
				relationPtr->setBlock(i, 1);
			}
		}
		retVal = 0;
	}
	//std::cout << "The relation now contains: " << std::endl;
	//std::cout << *relationPtr << std::endl;

	return retVal;
}
int Interpreter::HandleInsert()
{
	// insert into table-name(attrib,...) values(vals,...)
	// insert into table-name(attrib,...) select * from table2-name

	assert(mTokens[1].compare("into") == 0);
	assert(mTokens[3].compare("(") == 0);
	
	Relation* relationPtr = mSchemaManager.getRelation(mTokens[2]);

	std::vector<std::string> fieldNames;
	
	int retVal;

	unsigned int i = 4; // after opening parentheses (
	while(i < mTokens.size())
	{
		if(mTokens[i].compare(")") == 0)
			break;
		else if(mTokens[i].compare(",") == 0)
			++i;

		fieldNames.push_back(mTokens[i]); 
		++i;
	}
	
	++i; // after closing parentheses )

	if(mTokens[i].compare("values") == 0)
	{
		++i; // at opening parentheses (
		assert(mTokens[i].compare("(") == 0);

		++i; // after opening parentheses (
		
		Tuple tuple = relationPtr->createTuple();
		unsigned int fieldIndex = 0; // index in fieldNames vector

		while(i < mTokens.size())
		{
			if(mTokens[i].compare(")") == 0)
				break;
			else if(mTokens[i].compare(",") == 0)
				++i;
# if 0 //caribou
			if(mTokens[i].compare("\"") == 0)
			{
				++i; // at field value

				tuple.setField(fieldNames[fieldIndex], mTokens[i]);

				++i; // at closing quotation "
				assert(mTokens[i].compare("\"") == 0);

			}
			else
			{
				tuple.setField(fieldNames[fieldIndex], atoi(mTokens[i].c_str()));
			}

#elif 0
			if(relationPtr->getSchema().getFieldType(fieldNames[fieldIndex]) == INT)
				tuple.setField(fieldNames[fieldIndex], atoi(mTokens[i].c_str()));
			else
				tuple.setField(fieldNames[fieldIndex], mTokens[i]);
# else

			if(relationPtr->getSchema().getFieldType(fieldNames[fieldIndex]) == INT)
				tuple.setField(fieldNames[fieldIndex], atoi(mTokens[i].c_str()));
			else
			{
				// ignore the starting and ending "
				tuple.setField(fieldNames[fieldIndex], 
					mTokens[i].substr(1, mTokens[i].length() - 2));
			}
#endif

			++fieldIndex;
			++i;
		}
		assert(i == (mTokens.size() - 1));
		assert(fieldIndex == fieldNames.size());
	
		//std::cout << "Tuple: " << tuple << std::endl;

		retVal = AppendTupleToRelation(relationPtr, 5, tuple);
	}
	else if(mTokens[i].compare("select") == 0)
	{
		// select * from table-name

		++i; // at attrib name
		
		assert(mTokens[i].compare("*") == 0);
		++i; // at FROM
		assert(mTokens[i].compare("from") == 0);
		++i; // at table-name
	
		// assume that the same table is being selected and inserted
		// else, the attrib order and number of attribs is not accounted for
		assert(mTokens[i].compare(mTokens[2]) == 0);

		//TODO: handle holes later
		// bulk block handling:- till all blocks are done, read 10 blocks
		// at a time and append to relation. This method effectively doubles
		// the number of holes in the table, but the operation is performed
		// faster than inserting tuples one by one
		//
		int numBlocks = relationPtr->getNumOfBlocks();
		int blockCtr = 0;
		while(blockCtr < numBlocks)
		{
			int blocksToCopy = std::min(numBlocks - blockCtr, mMem.getMemorySize());
			relationPtr->getBlocks(blockCtr, 0, blocksToCopy);
			relationPtr->setBlocks(relationPtr->getNumOfBlocks(), 0, blocksToCopy);
			blockCtr += blocksToCopy;
		}
		retVal = 0;
	}
	else
	{
		std::cout << "Invalid INSERT query" << std::endl;
		return -1;
	}

	/*std::cout << "The relation now contains: " << std::endl;
	std::cout << *relationPtr << std::endl;
*/
	return retVal;
}


int Interpreter::AppendTupleToRelation
	(Relation* relation_ptr, int memory_block_index, Tuple& tuple)
{
	Block* block_ptr;
	if (relation_ptr->getNumOfBlocks()==0) 
	{
		//cout << "The relation is empty" << endl;
		//cout << "Get the handle to the memory block " 
			//<< memory_block_index << " and clear it" << endl;
		block_ptr=mMem.getBlock(memory_block_index);
		block_ptr->clear(); //clear the block
		block_ptr->appendTuple(tuple); // append the tuple
		//cout << "Write to the first block of the relation" << endl;
		if(relation_ptr->setBlock
			(relation_ptr->getNumOfBlocks(),memory_block_index) == false)
			return -1;
		else
			return 0;
	} 
	else 
	{
		//cout << "Read the last block of the relation into memory block " 
			//<< memory_block_index << endl;
		relation_ptr->getBlock(relation_ptr->getNumOfBlocks()-1,memory_block_index);
		block_ptr=mMem.getBlock(memory_block_index);

		if (block_ptr->isFull()) 
		{
			//cout << "(The block is full: Clear the memory block and append the tuple)" << endl;
			block_ptr->clear(); //clear the block
			block_ptr->appendTuple(tuple); // append the tuple
			//cout << "Write to a new block at the end of the relation" << endl;
			//write back to the relation
			if(relation_ptr->setBlock
				(relation_ptr->getNumOfBlocks(),memory_block_index)	== false)
				return -1;
			else
				return 0;
		} 
		else 
		{
			//cout << "(The block is not full: Append it directly)" << endl;
			block_ptr->appendTuple(tuple); // append the tuple
			//cout << "Write to the last block of the relation" << endl;
			//write back to the relation
			if(relation_ptr->setBlock
				(relation_ptr->getNumOfBlocks()-1,memory_block_index) == false)
				return -1;
			else
				return 0;
		}
	}  
}

int Interpreter::AppendTuplesToRelation
	(Relation* relation_ptr, int memory_block_index, vector <Tuple> & tuples)
{
	Block* block_ptr;
	if (relation_ptr->getNumOfBlocks()==0) 
	{
		block_ptr=mMem.getBlock(memory_block_index);
		block_ptr->clear(); //clear the block
	} 
	else 
	{
		relation_ptr->getBlock(relation_ptr->getNumOfBlocks()-1,memory_block_index);			//load the last block
		block_ptr=mMem.getBlock(memory_block_index);
		if (block_ptr->isFull ())					// the last block is full, so no use in loading it, just clear the copy in the memory
		{
			block_ptr->clear ();			
		}
	}	
	int index = 0;
	while (index < tuples.size ())
	{
		if (block_ptr->isFull()) 
		{
			relation_ptr->setBlock (relation_ptr->getNumOfBlocks(), memory_block_index);		// save the block to the end of the relation
			block_ptr->clear(); 
		}
		block_ptr->appendTuple(tuples [index]); 
		index ++;
	}
	if (!block_ptr->isEmpty ()) 
	{
		relation_ptr->setBlock (relation_ptr->getNumOfBlocks(), memory_block_index);		// save the block to the end of the relation
	}
	return 0;
}


bool Interpreter::CheckWhereOnTuple(const Tuple& tuple, 
					const std::vector<std::string>& whereRPN) const
{
	std::stack<std::string> runStack;
	
	Schema tupleSchema = tuple.getSchema();

	unsigned int wherePtr = 0, whereEnd = whereRPN.size();
	while(wherePtr != whereEnd)
	{
		if(is_operator(whereRPN[wherePtr]))
		{
			const std::string& op = whereRPN[wherePtr];

			if(op.compare("not") == 0) // the only single operand op
			{
				std::string operand = runStack.top();
				runStack.pop();

				if(operand.compare("true") == 0)
					runStack.push("false");
				else if(operand.compare("false") == 0)
					runStack.push("true");
				else
				{
					assert(false);
				}
			}
			else
			{
				// two operand operators
				std::string operand1 = runStack.top();
				runStack.pop();
				std::string operand2 = runStack.top();
				runStack.pop();

				if(op.compare(".") == 0)
				{
					//TODO: handle based on JOIN implementation
					
					return false;
				}
				else if((op.compare("and") == 0) || (op.compare("or") == 0))
				{
					// the top two of stack must be boolean

					bool operand1Val, operand2Val, resultVal;
					if(operand1.compare("true") == 0)
						operand1Val = true;
					else if(operand1.compare("false") == 0)
						operand1Val = false;
					else
						assert(false);

					if(operand2.compare("true") == 0)
						operand2Val = true;
					else if(operand2.compare("false") == 0)
						operand2Val = false;
					else
						assert(false);
	
					if(op.compare("and") == 0)
						resultVal = operand1Val && operand2Val;
					else
						resultVal = operand1Val || operand2Val;

					if(resultVal)
						runStack.push("true");
					else
						runStack.push("false");
				}
				else
				{
					// = > < + - * /
					// the operands can be table attribName, INT, STR20
					// the result can be INT or boolean

					bool stringArgs = false, intArgs = false;
					std::string operand1ValStr, operand2ValStr;
					int operand1ValInt = 0, operand2ValInt = 0;

					// whats operator1 like?
					if(operand1[0] == '"')
					{
						stringArgs = true;
						operand1ValStr = operand1.substr(1, operand1.length() - 2);
					}
					else if((operand1[0] >= '0') && (operand1[0] <= '9'))
					{
						intArgs = true;
						operand1ValInt = atoi(operand1.c_str());
					}
					else
					{
						// operand1 is some attrib name
						if(tupleSchema.getFieldType(operand1) == INT)
						{
							intArgs = true;
							operand1ValInt = tuple.getField(operand1).integer;
						}
						else
						{
							stringArgs = true;
							operand1ValStr = *(tuple.getField(operand1).str);
						}
					}
					assert(stringArgs != intArgs); // either one is set

					// whats operator2 like?
					if(operand2[0] == '"')
					{
						stringArgs = true;
						operand2ValStr = operand2.substr(1, operand2.length() - 2);
					}
					else if((operand2[0] >= '0') && (operand2[0] <= '9'))
					{
						intArgs = true;
						operand2ValInt = atoi(operand2.c_str());
					}
					else
					{
						// operand2 is some attrib name
						if(tupleSchema.getFieldType(operand2) == INT)
						{
							intArgs = true;
							operand2ValInt = tuple.getField(operand2).integer;
						}
						else
						{
							stringArgs = true;
							operand2ValStr = *(tuple.getField(operand2).str);
						}
					}
					assert(stringArgs != intArgs); // either one is set
			
					assert(!stringArgs || (op.compare("=") == 0)); // stringArgs -> equality

					if(stringArgs)
					{
						if(operand1ValStr.compare(operand2ValStr) == 0)
							runStack.push("true");
						else
							runStack.push("false");
					}
					else
					{
						int resultInt;
						bool pleasePushIntResult = false;

						switch(op[0])
						{
							case '=':
								if(operand1ValInt == operand2ValInt)
									runStack.push("true");
								else
									runStack.push("false");
							break;

							case '>':
								if(operand1ValInt > operand2ValInt)
									runStack.push("true");
								else
									runStack.push("false");
							break;

							case '<':
								if(operand1ValInt < operand2ValInt)
									runStack.push("true");
								else
									runStack.push("false");
							break;

							case '+':
								pleasePushIntResult = true;
								resultInt = operand1ValInt + operand2ValInt;
							break;

							case '-':
								pleasePushIntResult = true;
								resultInt = operand1ValInt - operand2ValInt;
							break;

							case '*':
								pleasePushIntResult = true;
								resultInt = operand1ValInt * operand2ValInt;
							break;

							case '/':
								pleasePushIntResult = true;
								resultInt = operand1ValInt / operand2ValInt;
							break;
						}

						if(pleasePushIntResult)
						{
							char resultStr[20];
							sprintf(resultStr, "%d", resultInt);
							runStack.push(resultStr);
						}
					}
				}
			}
		}
		else
		{
			runStack.push(whereRPN[wherePtr]);
		}

		++wherePtr;
	}

	assert(runStack.size() == 1);
	if(runStack.top().compare("true") == 0)
		return true;
	else if(runStack.top().compare("false") == 0)
		return false;
	else
		assert(false);
}

int Interpreter::GetCrossJoinedSchema(const std::vector<std::string>& tableNames, 
		std::vector<std::string>& resultFieldNames, 
		std::vector<enum FIELD_TYPE>& resultFieldTypes)
{

	char tableNumber[10];

	// Since cross join doesn't involve any projection, we don't really care
	for(unsigned int i = 0; i < tableNames.size(); ++i)
	{
		sprintf(tableNumber, "%d", i);

		std::vector<std::string> fieldNames = 
			mSchemaManager.getRelation(tableNames[i])->getSchema().getFieldNames();

		std::vector<enum FIELD_TYPE> fieldTypes = 
			mSchemaManager.getRelation(tableNames[i])->getSchema().getFieldTypes();

		
		for(unsigned int j = 0; j < fieldNames.size(); ++j)
		{
			resultFieldNames.push_back(fieldNames[j].append(tableNumber));
			resultFieldTypes.push_back(fieldTypes[j]);
		}
	}
	return 0;
}

std::string Interpreter::GetCrossJoinedName(std::vector<std::string>& tableNames) const
{
	std::string result(tableNames[0]);
	for(unsigned int i = 1; i < tableNames.size(); ++i)
	{
		result += "_";
		result += tableNames[i];
	}
	return result;
}

void Interpreter::naturaljoin ()
{
	std::vector<std::string> tables;
	tables.push_back (string ("course"));
	tables.push_back (string ("course2"));

	printf ("Got to the natural join place\n");
}

void Interpreter::printtableinfo (string tablename)
{
	Relation* relationPtr = mSchemaManager.getRelation (tablename);
	cout<<"[TABLE: "<< tablename<<"] NB: "<<relationPtr->getNumOfBlocks ()<< ", NT: "<<relationPtr->getNumOfTuples ()<<endl;
}

void Interpreter::sorttuples (vector<Tuple> &tuples, int orderindex)
{
	/// get the index of the orderby column in the relation schema
	for (int i=0; i<tuples.size (); i++)
	{
		tuples [i].setorderingindex (orderindex);
	}
	sort (tuples.begin (), tuples.end ());
}

void Interpreter::sorttuples (vector<Tuple> &tuples, vector<int> orderindices)
{
	/// get the index of the orderby column in the relation schema
	for (int i=0; i<tuples.size (); i++)
	{
		tuples [i].setorderingindices (orderindices);
	}
	sort (tuples.begin (), tuples.end ());
}

void Interpreter::printtabledata (Relation *rel_ptr)
{
	int totalblocks = rel_ptr->getNumOfBlocks ();
	int remblocks = totalblocks;
	int diskblock = 0;
	int mms = mMem.getMemorySize ();
	int doneblocks = 0;
	
	cout << "printing table" << endl;



	while (remblocks)
	{
		int nblocks = std::min (remblocks, mms);		// take min of mms and what is remaining
		rel_ptr->getBlocks (doneblocks, 0, nblocks);
		vector <Tuple> & tuples = mMem.getTuples (0, nblocks);
		remblocks -= nblocks;			// reduce what is remaining
		doneblocks += nblocks;

		for (int i=0; i<tuples.size (); i++)
		{
			cout << tuples [i] << endl;
		}
		
	}
}

void Interpreter::selectwithorderby (string table, string orderbycolumn)
{
		// sorting, because we have order by clause present
		int relindex = 0;
		int occupiedram = 0;	// to generalize it
		int mms = mMem.getMemorySize ();

		vector <int> ordind;
		ordind.push_back (2);
		//ordind.push_back (3);

		Relation * rel_ptr = mSchemaManager.getRelation (table);
		int rel_blocks = rel_ptr->getNumOfBlocks ();

		Schema& scm = rel_ptr->getSchema ();
		int orderindex = scm.getFieldOffset (orderbycolumn);

		if (rel_blocks + occupiedram <= mms)	// in memory, 1 pass
		{
			rel_ptr->getBlocks(0, 0, rel_blocks);
			std::vector<Tuple> tuples = mMem.getTuples (0, rel_blocks);
			sorttuples (tuples, orderindex);
		}
		else			// 2pass, do sorted sublists and then do multiway merge
		{
			// create a temprary table, it is actually to hold the sorted sublists
			// load the blocks from the relation, M blocks at a time
			// sort them and write them back in the temporary relation
			int totalblocks = rel_blocks;
			int remblocks = totalblocks;
			int doneblocks = 0;

			int avm = mms;
			int iterations = 0;
			vector <string> temptables;

			while (remblocks)
			{
				char temptablename [1000];
				sprintf (temptablename, "%s_order_%d", table.c_str (), iterations);
				printf ("Table: %s\n", temptablename);
				string tempname (temptablename);
				temptables.push_back (tempname);

				Relation * rel_ptr_temp = NULL;
				if((rel_ptr_temp = mSchemaManager.createRelation (tempname ,scm)) == NULL)
					return;

				//cout << rel_ptr << endl;

				int nblocks = std::min (remblocks, avm);						// take min of mms and what is remaining
				rel_ptr->getBlocks (doneblocks, 0, nblocks);	// load from the source block nblocks starting after doneblocks and put it in the beginning of the MM
				//cout << mMem <<endl;
				vector <Tuple> tuples = mMem.getTuples (0, nblocks);		// GET THE tuples
				sorttuples (tuples, orderindex);			//sort the tuples

				AppendTuplesToRelation (rel_ptr_temp, 0, tuples);
				remblocks -= nblocks;			// reduce what is remaining
				doneblocks += nblocks;			// increase what is done

				//printtabledata (rel_ptr_temp);
				iterations ++;
			}

			//// now do the merging of the sorted sublists
			// the sublists are inside relations temptables vector
			mergetables (temptables, ordind);
		}
		
}

// while ordering tuples, follow the indices in that order
void Interpreter::mergetables (vector<string> & tables, vector<int> col_indices)
{
	// so here we are merging sorted sublists of same table which are now given as a set of tables
	// so all the tables have the same schema

	// that means the number of sorted sublists is smaller the num_blocks in MM-1 (1 block for output), so that 1 block per sublist can be loaded
	assert (tables.size () < mMem.getMemorySize ()-1);	

	// load the relation pointers to the temp tables
	Relation* rel_ptr;
	vector <Tuple> tuples;
	vector <Tuple>::iterator it;
	
	// accumulate all the tuples in a single tuples vector
	for (int i=0; i<tables.size (); i++)
	{
		rel_ptr = mSchemaManager.getRelation (tables [i]);
		//assert (rel_ptr [i] != NULL);
		rel_ptr->getBlocks (0, 0, rel_ptr->getNumOfBlocks());		// read all the blocks to MM block 0
		vector <Tuple> temp = mMem.getTuples (0, rel_ptr->getNumOfBlocks());
		
		it = tuples.end ();
		tuples.insert (it, temp.begin (), temp.end ());
	}

	// sort the tuples
	sorttuples (tuples, col_indices [0]);
	
	string temp = "temp";
	createrelationfromtuples (tuples, temp);
}

void Interpreter::createrelationfromtuples (vector <Tuple> tuples, string giventablename)
{
	if (tuples.empty ()) return;
		
	Relation * rel_ptr = NULL;
	if((rel_ptr = mSchemaManager.createRelation (giventablename ,tuples [0].getSchema ())) == NULL)
		return;
	
	AppendTuplesToRelation (rel_ptr, 0, tuples);
	printtabledata (rel_ptr);
}








